package com.shopx.core.utils;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lituo
 * @version 1.0
 * @descript RSA加密工具类
 * @date 2021/11/17 9:35
 */
public class RSAUtils {
  public static final String CHARSET = "UTF-8";
  public static final String RSA_ALGORITHM = "RSA";

  /**
   * @descript 创建RSA加密对
   * @param keySize
   * @author lituo
   * @date 2021/11/17 9:46
   * @return java.util.Map<java.lang.String , java.lang.String>
   */
  public static Map<String, String> createKeys(int keySize) {
    KeyPairGenerator kpg;
    try {
      kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
    } catch (NoSuchAlgorithmException e) {
      throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
    }
    // 初始化KeyPairGenerator对象,密钥长度
    kpg.initialize(keySize);
    // 生成密匙对
    KeyPair keyPair = kpg.generateKeyPair();
    // 得到公钥
    Key publicKey = keyPair.getPublic();
    String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
    // 得到私钥
    Key privateKey = keyPair.getPrivate();
    String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
    // map装载公钥和私钥
    Map<String, String> keyPairMap = new HashMap<String, String>();
    keyPairMap.put("publicKey", publicKeyStr);
    keyPairMap.put("privateKey", privateKeyStr);
    // 返回map
    return keyPairMap;
  }

  /**
   * @descript 获取公钥
   * @param publicKey
   * @author lituo
   * @date 2021/11/17 9:47
   * @return java.security.interfaces.RSAPublicKey
   */
  public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
    KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
    X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
    RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
    return key;
  }

  /**
   * @descript 获取私钥
   * @param privateKey
   * @author lituo
   * @date 2021/11/17 9:46
   * @return java.security.interfaces.RSAPrivateKey
   */
  public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
    KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
    PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
    RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
    return key;
  }

  /**
   * @descript 公钥加密
   * @param data
   * @param publicKey
   * @author lituo
   * @date 2021/11/17 9:46
   * @return java.lang.String
   */
  public static String publicEncrypt(String data, RSAPublicKey publicKey) {
    try {
      Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
      cipher.init(Cipher.ENCRYPT_MODE, publicKey);
      return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength()));
    } catch (Exception e) {
      throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
    }
  }


  /**
   * @descript 私钥解密
   * @param data
   * @param privateKey
   * @author lituo
   * @date 2021/11/17 9:47
   * @return java.lang.String
   */
  public static String privateDecrypt(String data, RSAPrivateKey privateKey) {
    try {
      Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
      cipher.init(Cipher.DECRYPT_MODE, privateKey);
      return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), privateKey.getModulus().bitLength()), CHARSET);
    } catch (Exception e) {
      throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
    }
  }

  /**
   * @descript 私钥加密
   * @param data
   * @param privateKey
   * @author lituo
   * @date 2021/11/17 9:47
   * @return java.lang.String
   */
  public static String privateEncrypt(String data, RSAPrivateKey privateKey) {
    try {
      Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
      cipher.init(Cipher.ENCRYPT_MODE, privateKey);
      return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength()));
    } catch (Exception e) {
      throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
    }
  }

  /**
   * @descript 公钥解密
   * @param data
   * @param publicKey
   * @author lituo
   * @date 2021/11/17 9:47
   * @return java.lang.String
   */
  public static String publicDecrypt(String data, RSAPublicKey publicKey) {
    try {
      Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
      cipher.init(Cipher.DECRYPT_MODE, publicKey);
      return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), publicKey.getModulus().bitLength()), CHARSET);
    } catch (Exception e) {
      throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
    }
  }

  private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
    int maxBlock = 0;
    if (opmode == Cipher.DECRYPT_MODE) {
      maxBlock = keySize / 8;
    } else {
      maxBlock = keySize / 8 - 11;
    }
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    int offSet = 0;
    byte[] buff;
    int i = 0;
    try {
      while (datas.length > offSet) {
        if (datas.length - offSet > maxBlock) {
          //可以调用以下的doFinal（）方法完成加密或解密数据：
          buff = cipher.doFinal(datas, offSet, maxBlock);
        } else {
          buff = cipher.doFinal(datas, offSet, datas.length - offSet);
        }
        out.write(buff, 0, buff.length);
        i++;
        offSet = i * maxBlock;
      }
    } catch (Exception e) {
      throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
    }
    byte[] resultDatas = out.toByteArray();
    IOUtils.closeQuietly(out);
    return resultDatas;
  }


  //测试
  public static void main(String[] args) throws Exception {
    //获取公私钥加密对
//    Map<String, String> keyMap = RSAUtils.createKeys(1024);
//    String publicKey = keyMap.get("publicKey");
//    String privateKey = keyMap.get("privateKey");
//    System.out.println("公钥: \n\r" + publicKey);
//    System.out.println("私钥： \n\r" + privateKey);

    String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCbPTlB63OSW-2N9tIArI3sJ1eVwwin7JPH4RaWdMqEa1FjKyuPB-0njv6Ldk64kqEqfihRh6ryH9GF1uqQZZdya7krxPoO6SoXKaBDZAo63Bz-iz-1LQqJDll3H0TtuJuxB-GJDnFGM8KBh51P4Cpd6vPhYC4GPqg7aaraON8hqQIDAQAB";
    String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJs9OUHrc5Jb7Y320gCsjewnV5XDCKfsk8fhFpZ0yoRrUWMrK48H7SeO_ot2TriSoSp-KFGHqvIf0YXW6pBll3JruSvE-g7pKhcpoENkCjrcHP6LP7UtCokOWXcfRO24m7EH4YkOcUYzwoGHnU_gKl3q8-FgLgY-qDtpqto43yGpAgMBAAECgYEAmpI4e9mcRuP_wjY73e3ylmyJ-hWrfcDDWOdXnVIHABj3fd3TTmSfD5-C9oxDT-a4UN4y-RMz9EgYUMKw5jwxsXA6B-dawpawWjOXuAera7gQjAnCp3KhqebLEQdp4orRLe7EHFBi8mA8nBUYNYA1iPiPI2LHU30zcRIiWTDw-50CQQDOAuFK8EagTkI9oynJq1pVJY0kx2B675i2qy4fRgOc7rqPayurPC9B9HTAOIj4r1hprzOYAC_e_3FV18iFHK6bAkEAwOh0aK4Qqtx2uaXsSj55knjOSyCgN7d8pTpwBFfFr_8L0fOMt6mH_rY7UMVHO-ytFBzBA18ZLXXByibIQ9xzCwJAcSKlxTjVIH455I2xij4fOrmHnNYdhwYD0PWLO8OAgnOcYtzRO5WuwkLUxyeFGUV6gXelFumrYITaIfGB_pA01wJAW2GCS1gxcw7OevD7dswTgOwTJQmGe_5CYG7HQ3dkoRNbQRBzF0mKr4EZphBdxyg9cOqSEAvLfHps21r1wRe7swJAODs7gP4HVUTCmLDpFIn54gmZN9rnysJq8dFo0LPCFvJTMijJwy7t4qWQnL5T1R8BATm0KlA6MTHbUSuSkKTryw";


    String str = "20200105123400001:gHPM12RhV2Eb0OrH";
    //公钥加密，私钥解密
    String encodedData = RSAUtils.publicEncrypt(str, RSAUtils.getPublicKey(publicKey));
    System.out.println("公钥加密密文：\r\n" + encodedData);

    String decodedData = RSAUtils.privateDecrypt(encodedData, RSAUtils.getPrivateKey(privateKey));
    System.out.println("私钥解密明文: \r\n" + decodedData);

    //私钥加密，公钥解密
//    String encodedData2 = RSAUtils.privateEncrypt(str, RSAUtils.getPrivateKey(privateKey));
//    System.out.println("私钥加密密文：\r\n" + encodedData2);
//
//    String decodedData2 = RSAUtils.publicDecrypt(encodedData2, RSAUtils.getPublicKey(publicKey));
//    System.out.println("私钥解密明文: \r\n" + decodedData2);
  }
}
